﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using FRW.Common.Extensions;
using Money;
using Money.Plugins;

namespace Money.Interface
{
    // TODO Add the following functionality, most likely in this base class
    // Handle click events to drill down into data (Use HitTest for doing this)
    // Add a context menu to allow varying options for drill down of data
    // TODO add an icon to show when we have zoomed into data and esc key then takes us back
    // TODO esc key to remove the zoom only works on the first graph but not when changed?
    // TODO update graph to support diaplying in month range or day range (maybe even week?)

    public partial class GraphControl : UserControl
    {
        protected ChartArea _chartArea;
        protected DateTime _startDate;
        protected DateTime _endDate;
        protected IDataSet _dataInterface;
        protected string _category;
        protected string _subCategory;
        protected string _account;

        public GraphControl()
        {
            InitializeComponent();

            _zoomIndicator.Visible = false;

            _chartArea = _graph.ChartAreas[0];

            // lets configure grids etc. when graph needs to be drawn
            _graph.PrePaint += new EventHandler<ChartPaintEventArgs>(_graph_PrePaint);

            // TODO need to set default option for each graph type somewhere
            // mainGraph.ChartAreas[0].AxisX.Interval = 1D;
            _chartArea.AxisX.IsLabelAutoFit = false;
            _chartArea.AxisX.LabelStyle.Angle = -90;

            _graph.AntiAliasing = AntiAliasingStyles.All;
            _graph.TextAntiAliasingQuality = TextAntiAliasingQuality.High;

            List<Axis> axes = new List<Axis>();
            axes.Add(_chartArea.AxisX);
            axes.Add(_chartArea.AxisY);

            // Enable range selection and zooming end user interface
            _chartArea.CursorX.IsUserEnabled = true;
            _chartArea.CursorX.IsUserSelectionEnabled = true;
            _chartArea.CursorY.IsUserEnabled = true;
            _chartArea.CursorY.IsUserSelectionEnabled = true;
            
            _chartArea.AxisX.ScrollBar.IsPositionedInside = false;
            _chartArea.AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.ResetZoom;
            _chartArea.AxisY.ScrollBar.ButtonColor = Color.LightGray;
            _chartArea.AxisY.MajorGrid.Interval = 1000;
            _chartArea.AxisY.MinorGrid.Interval = 100;
            _chartArea.AxisX.LabelStyle.Format = "MMM-yyyy";
            _chartArea.AxisX.LabelStyle.ForeColor = Color.DarkSlateGray;

            foreach(Axis axis in axes)
            {
                axis.ScaleView.Zoomable = true;
                axis.MinorGrid.LineColor = Color.Gray;
                axis.MajorGrid.LineColor = Color.Gray;
                axis.MinorGrid.LineDashStyle = ChartDashStyle.Dot;
                axis.MajorTickMark.LineColor = Color.Gray;
                axis.MinorGrid.Enabled = true;
                axis.LineWidth = 2;
                axis.LineColor = Color.DarkSlateGray;
                Font font = new System.Drawing.Font("Lucida Sans", 10.0f);
                axis.LabelStyle.Font = font;
                axis.LabelStyle.ForeColor = Color.DarkSlateGray;
            }

            //Set the border style
            _graph.BorderSkin.SkinStyle = BorderSkinStyle.None;
            _graph.BorderSkin.BackColor = Color.White;
            _graph.BorderSkin.BorderColor = Color.Gray;
            _graph.BorderSkin.BorderDashStyle = ChartDashStyle.Solid;

            // Configure the chart area style
            _chartArea.BackColor = Color.White;
            _chartArea.BorderColor = Color.LightGray;
            _chartArea.BorderDashStyle = ChartDashStyle.Solid;
            _chartArea.BorderWidth = 1;
//            _chartArea.ShadowOffset = 3;

            //Range = GraphDateRange.MONTHS;

        }

        void _graph_PrePaint(object sender, ChartPaintEventArgs e)
        {
            updateGraphAxes();
        }

        protected virtual void updateGraphAxes()
        {
            _chartArea.AxisX.IntervalType = DateTimeIntervalType.Months;
            _chartArea.AxisX.Interval = 1;

            _chartArea.AxisY.MinorGrid.Interval = 100;

            if ((_chartArea.AxisY.ScaleView.ViewMaximum - _chartArea.AxisY.ScaleView.ViewMinimum) > _graph.Height * 10)
            {
                if ((_chartArea.AxisY.ScaleView.ViewMaximum - _chartArea.AxisY.ScaleView.ViewMinimum) >= 10000)
                {
                    _chartArea.AxisY.MajorGrid.Interval = 10000;
                    _chartArea.AxisY.MinorGrid.Interval = 5000;
                    _chartArea.AxisY.MinorGrid.Enabled = true;
                }
                else if ((_chartArea.AxisY.ScaleView.ViewMaximum - _chartArea.AxisY.ScaleView.ViewMinimum) >= 1000)
                {
                    _chartArea.AxisY.MinorGrid.Interval = 500;
                    _chartArea.AxisY.MinorGrid.Enabled = true;
                }
                else  if ((_chartArea.AxisY.ScaleView.ViewMaximum - _chartArea.AxisY.ScaleView.ViewMinimum) >= 200)
                {
                    _chartArea.AxisY.MinorGrid.Interval = 100;
                    _chartArea.AxisY.MinorGrid.Enabled = false;
                }
                else
                {
                    _chartArea.AxisY.MinorGrid.Interval = 20;
                    _chartArea.AxisY.MinorGrid.Enabled = true;
                }
            }
            else
            {
                if ((_chartArea.AxisY.ScaleView.ViewMaximum - _chartArea.AxisY.ScaleView.ViewMinimum) >= 2000)
                {
                    _chartArea.AxisY.MinorGrid.Interval = 1000;
                }
                else if ((_chartArea.AxisY.ScaleView.ViewMaximum - _chartArea.AxisY.ScaleView.ViewMinimum) >= 200)
                {
                    _chartArea.AxisY.MinorGrid.Interval = 100;
                }
                else
                {
                    _chartArea.AxisY.MinorGrid.Interval = 20;
                }
                _chartArea.AxisY.MinorGrid.Enabled = true;
            }

            if ((_chartArea.AxisX.ScaleView.ViewMaximum - _chartArea.AxisX.ScaleView.ViewMinimum) > 120)
            {
                _chartArea.AxisX.MajorGrid.IntervalType = DateTimeIntervalType.Months;
                _chartArea.AxisX.MajorGrid.Interval = 1;
                _chartArea.AxisX.MinorGrid.IntervalType = DateTimeIntervalType.Weeks;
                _chartArea.AxisX.MinorGrid.Interval = 1;
                _chartArea.AxisX.MinorGrid.Enabled = false;
                _chartArea.AxisX.MajorTickMark.IntervalType = DateTimeIntervalType.Months;
                _chartArea.AxisX.MajorTickMark.Interval = 1;
                _chartArea.AxisX.LabelStyle.IntervalType = DateTimeIntervalType.Months;
            }
            else if ((_chartArea.AxisX.ScaleView.ViewMaximum - _chartArea.AxisX.ScaleView.ViewMinimum) > 60)
            {
                _chartArea.AxisX.MajorGrid.IntervalType = DateTimeIntervalType.Months;
                _chartArea.AxisX.MajorGrid.Interval = 1;
                _chartArea.AxisX.MinorGrid.IntervalType = DateTimeIntervalType.Weeks;
                _chartArea.AxisX.MinorGrid.Interval = 1;
                _chartArea.AxisX.MinorGrid.Enabled = true;
                _chartArea.AxisX.MajorTickMark.IntervalType = DateTimeIntervalType.Months;
                _chartArea.AxisX.MajorTickMark.Interval = 1;
                _chartArea.AxisX.LabelStyle.IntervalType = DateTimeIntervalType.Months;
            }
            else
            {
                _chartArea.AxisX.MajorGrid.IntervalType = DateTimeIntervalType.Weeks;
                _chartArea.AxisX.MajorGrid.Interval = 1;
                _chartArea.AxisX.MinorGrid.IntervalType = DateTimeIntervalType.Days;
                _chartArea.AxisX.MinorGrid.Interval = 1;
                _chartArea.AxisX.MinorGrid.Enabled = true;
                _chartArea.AxisX.MajorTickMark.IntervalType = DateTimeIntervalType.Weeks;
                _chartArea.AxisX.MajorTickMark.Interval = 1;
                _chartArea.AxisX.LabelStyle.IntervalType = DateTimeIntervalType.Weeks;
            }

            if ((_chartArea.AxisX.ScaleView.ViewMaximum - _chartArea.AxisX.ScaleView.ViewMinimum) > 365)
            {
                _chartArea.AxisX.LabelStyle.Interval = 5;
            }
            else
            {
                _chartArea.AxisX.LabelStyle.Interval = 1;
            }
        }

        public UserControl MainControl
        {
            get
            {
                return this;
            }
        }

        private void mainGraph_MouseClick(object sender, MouseEventArgs e)
        {
            string graphType  = _graph.Tag as string;

            HitTestResult result = _graph.HitTest(e.X, e.Y);

            if (e.Clicks > 1)
            {
            }

            switch (result.ChartElementType)
            {
                case ChartElementType.DataPoint:
                    string category = result.Series.Name;
                    DataPoint datapoint = result.Object as DataPoint;
                    DateTime date = DateTime.FromOADate(datapoint.XValue);

                    _startDate = date.StartOfMonth();
                    _endDate = date.EndOfMonth();
                    _account = "";

                    switch (graphType)
                    {
                        case "Categories":
                            _category = category;
                            _subCategory = "";
                            break;

                        case "SubCategories":
                            _subCategory = category;
                            _category = "";
                            break;

                        default:
                            break;
                    }
                    OnFiltered();
                    break;

                case ChartElementType.LegendItem:
                    LegendItem legend = result.Object as LegendItem;
                    if (legend != null)
                    {
                        _startDate = _endDate = new DateTime(1700, 1, 1);
                        _account = "";

                        switch (graphType)
                        {
                            case "Categories":
                                _category = legend.Name;
                                _subCategory = "";
                                break;

                            case "SubCategories":
                                _category = "";
                                _subCategory = legend.Name;
                                break;

                            default:
                                break;
                        }
                        OnFiltered();
                    }
                    break;
            }

        }

        public void ResetGraph()
        {
            if (_chartArea.AxisX.ScaleView.IsZoomed)
            {
                _chartArea.AxisX.ScaleView.ZoomReset(10);
            }
            if (_chartArea.AxisY.ScaleView.IsZoomed)
            {
                _chartArea.AxisY.ScaleView.ZoomReset(10);
            }

            _zoomIndicator.Visible = false;

            // reset the filter that we may have applied
            _startDate = _endDate = new DateTime(1700, 1, 1);

            _account = "";
            _category = "";
            _subCategory = "";

            OnFiltered();
        }

        protected void HidePanel()
        {
            _splitContainer.Panel1Collapsed = true;
        }

        protected virtual DateTime[] LimitFilterDates(DateTime startDate, DateTime endDate)
        {
            DateTime[] newDates = new DateTime[2];

            newDates[0] = startDate.StartOfMonth();
            newDates[1] = endDate.EndOfMonth();

            return newDates;
        }

        /// <summary>
        /// TODO should this be handled in each of the graphs rather than centrally?
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _graph_AxisViewChanged(object sender, ViewEventArgs e)
        {
            if (e.Axis == _chartArea.AxisX)
            {
                try
                {
                    DateTime startDate = DateTime.FromOADate(e.NewPosition);
                    DateTime endDate = DateTime.FromOADate(e.NewPosition + e.NewSize);

                    if (startDate.Year < 1980 || endDate.Year < 1980)
                    {
                        return;
                    }

                    string graphType = _graph.Tag as string;

                    _startDate = startDate;
                    _endDate = endDate;

                    OnFiltered();

                    _chartArea.AxisX.ScrollBar.Enabled = false;
                    _chartArea.AxisY.ScrollBar.Enabled = false;
                }
                catch
                {
                    // reset the filter that we may have applied
                    _startDate = _endDate = new DateTime(1700, 1, 1);
                    _account = "";
                    _category = "";
                    _subCategory = "";

                    OnFiltered();
                }
            }
            else if (e.Axis == _chartArea.AxisY)
            {
                if ((e.Axis.ScaleView.ViewMaximum - e.Axis.ScaleView.ViewMinimum) > 2000)
                {
                    _chartArea.AxisY.MinorGrid.Enabled = false;
                }
                else
                {
                    _chartArea.AxisY.MinorGrid.Enabled = true;
                }
            }
        }

        #region IGraph Members

        public virtual void UpdateGraph(object sender, DateChangedEventArgs e)
        {
        }

        #endregion

        private void _graph_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Escape:
                    e.IsInputKey = true;
                    break;
            }
        }

        private void _graph_KeyPress(object sender, KeyPressEventArgs e)
        {
        }

        private void _graph_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Escape:
                    if (_chartArea.AxisX.ScaleView.IsZoomed || _chartArea.AxisY.ScaleView.IsZoomed)
                    {
                        _chartArea.AxisX.ScaleView.ZoomReset(100);
                        _chartArea.AxisY.ScaleView.ZoomReset(100);
                    }
                    e.Handled = true;
                    break;
            }
        }

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            if (_chartArea.AxisX.ScaleView.IsZoomed || _chartArea.AxisY.ScaleView.IsZoomed)
            {
                _chartArea.AxisX.ScaleView.ZoomReset(100);
                _chartArea.AxisY.ScaleView.ZoomReset(100);
            }
        }

        /// <summary>
        /// Called each time we are going to zoom into data or pan.
        /// At present we always align data to months so a full month will always be summarised.
        /// TODO Make the decision of label and markers etc more granular
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _graph_AxisViewChanging(object sender, ViewEventArgs e)
        {
            if (e.Axis == _chartArea.AxisX)
            {
                try
                {
                    DateTime[] filterDates;

                    DateTime startDate = DateTime.FromOADate(e.NewPosition);
                    DateTime endDate = DateTime.FromOADate(e.NewPosition + e.NewSize);

                    filterDates = LimitFilterDates(startDate, endDate);

                    e.NewPosition = filterDates[0].ToOADate();
                    double endPoint = filterDates[1].ToOADate();
                    e.NewSize = endPoint - e.NewPosition;
                }
                catch
                {
                }
            }
            else if (e.Axis == _chartArea.AxisY)
            {
                int divisor = 100;

                if (e.NewSize >= 10000)
                {
                    divisor = 10000;
                }
                else if (e.NewSize >= 1000)
                {
                    divisor = 1000;
                }

                // round the axis so that we always have grid and tick marks on multiples of 100 or 1000
                e.NewPosition = (Math.Round(e.NewPosition / divisor, 0) * divisor) - divisor;
                e.NewSize = (Math.Round(e.NewSize / divisor, 0) * divisor) +  (2 * divisor);
            }

            _zoomIndicator.Visible = true;
        }

        public void SetDataInterface(IDataSet dataInterface)
        {
            _dataInterface = dataInterface;
        }
        
        public event FilterEventHandler ApplyFilter;

        protected void OnFiltered()
        {
            if (ApplyFilter != null)
            {
                FilterEventArgs args = new FilterEventArgs(_startDate, _endDate, _category, _subCategory, _account);

                ApplyFilter(this, args);
            }
        }

        private void _zoomIndicator_Click(object sender, EventArgs e)
        {
            if (_chartArea.AxisX.ScaleView.IsZoomed || _chartArea.AxisY.ScaleView.IsZoomed)
            {
                _chartArea.AxisX.ScaleView.ZoomReset(100);
                _chartArea.AxisY.ScaleView.ZoomReset(100);

                ApplyFilter(this, new FilterEventArgs());
            }

            _zoomIndicator.Visible = false;
        }
    }
}
